import * as path from "path";
import { MapLike } from "./types";

export const hasOwnProperty = Object.prototype.hasOwnProperty;

export function hasProperty(map: MapLike<any>, key: string): boolean {
    return hasOwnProperty.call(map, key);
}

export function getProperty<T>(map: MapLike<T>, key: string): T | undefined {
    return hasOwnProperty.call(map, key) ? map[key] : undefined;
}

export function getOwnKeys<T>(map: MapLike<T>): string[] {
    const keys: string[] = [];
    for (const key in map) {
        if (hasOwnProperty.call(map, key)) {
            keys.push(key);
        }
    }
    return keys;
}

export function getOwnValues<T>(obj: T[]): T[] {
    const values: T[] = [];
    for (const key in obj) {
        if (hasOwnProperty.call(obj, key)) {
            values.push(obj[key]);
        }
    }
    return values;
}

const _entries = Object.entries || (<T>(obj: MapLike<T>) => {
    const keys = getOwnKeys(obj);
    const result: [string, T][] = Array(keys.length);
    for (let i = 0; i < keys.length; i++) {
        result[i] = [keys[i], obj[keys[i]]];
    }
    return result;
});

export function getMapEntries<T>(obj: MapLike<T>): [string, T][] {
    return obj ? _entries(obj) : [];
}

export function getObjectArrayMapEntries<T>(array: readonly T[], callback: (value: T) => string): [string, T][] {
    const result: [string, T][] = Array(array.length);
    for (let i = 0; i < array.length; i++) {
        const element = array[i];
        result[i] = [callback(element), element];
    }
    return result;
}

export function stringEqual(s1: string, s2: string): boolean;
export function stringEqual(s: string, strs: string[]): boolean;
export function stringEqual(s1: string, s2: string | string[]): boolean {
    const lowerStr = s1.toLowerCase();
    return Array.isArray(s2) ? s2.find(e => lowerStr === e.toLowerCase()) !== undefined : lowerStr === s2.toLowerCase();
}

export function stringIsOctalNumber(str: string): boolean {
    return /^&(O|o)[0-7]+$/.test(str);
}

export function stringIsHexNumber(str: string): boolean {
    return /^&(H|h)[0-9a-fA-F]+$/.test(str);
}

export function stringIsInteger(str: string): boolean {
    return /^((\s*\-\s*)?\d+)|(&(H|h)[0-9a-fA-F]+)|(&(O|o)[0-7]+)$/.test(str);
}

export function stringIsFloat(str: string): boolean {
    return /^(\s*\-\s*)?\d*\.\d+$/.test(str);
}

export function stringIsNumber(str: string): boolean {
    return /^((\s*\-\s*)?\d+)|(&(H|h)[0-9a-fA-F]+)|(&(O|o)[0-7]+)|(\d*\.\d+)$/.test(str);
}

export function stringIsBoolean(str: string): boolean {
    const lower = str.toLowerCase().trim();
    return lower === "true" || lower === "false";
}

export function stringToInteger(str?: string): number | undefined {
    return str && stringIsInteger(str) ? Number.parseInt(str) : undefined;
}

export function stringToFloat(str?: string): number | undefined {
    return str && stringIsFloat(str) ? Number.parseFloat(str) : undefined;
}

export function stringToNumber(str?: string, defaultValue: number = 0): number {
    if (!str || str.length === 0) {
        return defaultValue;
    }
    return stringIsOctalNumber(str) ? Number.parseInt(str.substring(2), 8) :
        stringIsHexNumber(str) ? Number.parseInt(str.substring(2), 16) :
        stringIsInteger(str) ? Number.parseInt(str) :
        stringIsFloat(str) ? Number.parseFloat(str) : defaultValue;
}

export function insertIntoArray<T>(array: T[], item: T, fromRight: boolean, callback: (item: T) => boolean) {
    if (array.length === 0) {
        array.push(item);
        return;
    }
    if (fromRight) {
        for (let i = array.length - 1; i >= 0; i--) {
            if (callback(array[i])) {
                if (i === array.length - 1) {
                    array.push(item);
                }
                else {
                    array.splice(i + 1, 0, item);
                }
                return;
            }
        }
        array.unshift(item);
    }
    else {
        for (let i = 0; i < array.length; i++) {
            if (callback(array[i])) {
                if (i === 0) {
                    array.unshift(item);
                }
                else {
                    array.splice(i - 1, 0, item);
                }
                return;
            }
        }
        array.push(item);
    }
}

export function deleteArrayItem<T>(array: T[], predicate: (item: T) => boolean) {
    let index = array.findIndex(predicate);
    if (index > -1) {
        array.splice(index, 1);
    }
}

// sleep
export function sleep(ms: number) {
    return new Promise((resolve) => { setTimeout(resolve, ms); });
}

export function findArray<T, E>(array: T[], searcher: (arg1: T) => E | undefined): E | undefined {
    for (const item of array) {
        let result = searcher(item);
        if (result) {
            return result;
        }
    }
}

export function createMap<K, V>(items: readonly V[], keyGetter: (e: V) => K): Map<K, V> {
    const map = new Map<K, V>();
    for (const ele of items) {
        map.set(keyGetter(ele), ele);
    }
    return map;
}

const supportedExten = new Set([ ".ini", ".inc", ".dms", ".mrs", ".bat" ]);

export function isSupportedFile(fsPath: string) {
    return supportedExten.has(path.extname(fsPath).toLowerCase());
}

export function isSupportedTriggerFile(fsPath: string) {
    let exten = path.extname(fsPath).toLowerCase();
    return supportedExten.has(exten) || exten === ".mdd";
}
